spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { EnabledToggle, RunNowButton } from '@/components/admin/actions';4import { AdminError, AdminHeader, JsonInline, RunsTable, SectionTitle } from '@/components/admin/ui';5import { Pagination } from '@/components/ui/pagination';6import { adminApi, safeAdmin } from '@/lib/admin-api';7import { fmtAgo, fmtDateTime } from '@/lib/format';89export async function generateMetadata({ params }: { params: Promise<{ name: string }> }): Promise<Metadata> {10 const { name } = await params;11 return { title: `${name} runs` };12}1314export default async function AdminConnectorPage({ params, searchParams }: { params: Promise<{ name: string }>; searchParams: Promise<{ page?: string }> }) {15 const { name } = await params;16 const sp = await searchParams;17 const page = Math.max(1, Number(sp.page) || 1);18 const [runsRes, overviewRes] = await Promise.all([safeAdmin(adminApi.connectorRuns(name, page, 50)), safeAdmin(adminApi.overview())]);19 const connector = overviewRes.data?.data.connectors.find((c) => c.name === name) ?? null;20 const now = Date.now();21 return (22 <>23 <AdminHeader24 title={<span className="mono">{name}</span>}25 lede={connector ? `${connector.source_name} · ${connector.description ?? ''}` : 'Connector runs and errors'}26 action={27 <div className="flex flex-wrap items-center gap-2">28 <RunNowButton name={name} />29 {connector && <EnabledToggle name={name} enabled={connector.enabled} />}30 <Link href="/admin" className="text-sm text-accent hover:underline">31 ← Overview32 </Link>33 </div>34 }35 />36 {connector && (37 <dl className="grid grid-cols-2 gap-3 text-sm md:grid-cols-4">38 <div>39 <dt className="eyebrow">Enabled</dt>40 <dd className={connector.enabled ? 'text-active' : 'text-warn'}>{connector.enabled ? 'yes' : 'no'}</dd>41 </div>42 <div>43 <dt className="eyebrow">Last success</dt>44 <dd title={fmtDateTime(connector.last_success_at)}>{fmtAgo(connector.last_success_at, now)}</dd>45 </div>46 <div>47 <dt className="eyebrow">Last attempt</dt>48 <dd title={fmtDateTime(connector.last_attempt_at)}>{fmtAgo(connector.last_attempt_at, now)}</dd>49 </div>50 <div>51 <dt className="eyebrow">Next run</dt>52 <dd>{fmtDateTime(connector.next_run_at)}</dd>53 </div>54 <div className="col-span-2 md:col-span-4">55 <dt className="eyebrow">Config</dt>56 <dd>57 <JsonInline value={connector.config} />58 </dd>59 </div>60 </dl>61 )}6263 {runsRes.error !== null ? (64 <div className="mt-6">65 <AdminError message={runsRes.error} />66 </div>67 ) : (68 <>69 <SectionTitle>Runs</SectionTitle>70 <RunsTable runs={runsRes.data.data} now={now} showConnector={false} emptyLabel="This connector has never run" />71 <Pagination className="mt-4" page={runsRes.data.pagination.page} pages={runsRes.data.pagination.pages} total={runsRes.data.pagination.total} pageSize={runsRes.data.pagination.page_size} makeHref={(p) => `/admin/connectors/${encodeURIComponent(name)}?page=${p}`} />7273 <SectionTitle>Recent errors</SectionTitle>74 {runsRes.data.errors.length === 0 ? (75 <p className="rounded-md border border-dashed border-rule-strong px-4 py-6 text-center text-sm text-ink-3">No errors recorded</p>76 ) : (77 <ul className="divide-y divide-rule border-y border-rule">78 {runsRes.data.errors.map((e) => (79 <li key={e.id} className="grid gap-1 py-2.5 text-sm sm:grid-cols-[160px_140px_minmax(0,1fr)] sm:gap-4">80 <span className="text-ink-2" title={fmtDateTime(e.occurred_at)}>81 {fmtAgo(e.occurred_at, now)}82 </span>83 <span className="mono text-xs text-danger">{e.error_type ?? 'Error'}</span>84 <span className="min-w-0">85 <span className="mono break-words text-xs text-ink">{e.message ?? '—'}</span>86 {e.run_id && <span className="mono ml-2 text-[11px] text-ink-3">{e.run_id}</span>}87 {e.context && <JsonInline value={e.context} className="mt-1" />}88 </span>89 </li>90 ))}91 </ul>92 )}93 </>94 )}95 </>96 );97}98